package com.atguigu.tingshu.common.login;

import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.user.UserInfo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Slf4j
@Aspect
@Component
public class GuiguLoginAspect {

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Around("execution(* com.atguigu.tingshu.*.api.*.*(..)) && @annotation(guigulogin)")
    public Object loginAround(ProceedingJoinPoint joinPoint, GuiguLogin guigulogin) throws Throwable {
        try {
            // 1. 获取请求信息
            ServletRequestAttributes requestAttributes = getRequestAttributes();
            HttpServletRequest request = requestAttributes.getRequest();

            // 2. 尝试获取token
            String token = request.getHeader("token");

            if (!StringUtils.isEmpty(token)) {
                // 3. 处理token并设置用户上下文
                processTokenAndSetContext(token);
            }

            // 4. 如果需要强制登录但用户未登录，抛出异常
            if (guigulogin.required() && !AuthContextHolder.isLoggedIn()) {
                log.warn("Login required but not authenticated. URI: {}, IP: {}",
                        request.getRequestURI(), request.getRemoteAddr());
                throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
            }

            // 5. 执行目标方法
            return joinPoint.proceed();

        } catch (GuiguException e) {
            log.error("Authentication error: {}", e.getMessage());
            throw e;
        } catch (Exception e) {
            log.error("System error during authentication", e);
            throw new GuiguException(ResultCodeEnum.SERVICE_ERROR);
        } finally {
            // 6. 清理上下文
            AuthContextHolder.removeUserId();
            AuthContextHolder.removeUsername();
            log.debug("Cleared authentication context");
        }
    }

    private void processTokenAndSetContext(String token) {
        try {
            UserInfo userInfo = getUserInfoFromRedis(token);
            if (userInfo != null) {
                AuthContextHolder.setUserId(userInfo.getId());
                AuthContextHolder.setUsername(userInfo.getNickname());
                log.debug("User authenticated: userId={}", userInfo.getId());
            }
        } catch (Exception e) {
            log.error("Failed to process token", e);
            // 不阻断流程，仅记录错误
        }
    }

    private ServletRequestAttributes getRequestAttributes() {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            log.error("Request context not available");
            throw new GuiguException(ResultCodeEnum.SERVICE_ERROR);
        }
        return (ServletRequestAttributes) attributes;
    }

    private UserInfo getUserInfoFromRedis(String token) {
        try {
            // 从Redis中获取用户信息
            String redisKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
            UserInfo userInfo = (UserInfo) redisTemplate.opsForValue().get(redisKey);
            if (userInfo == null) {
                log.warn("Invalid or expired token: {}", token);
                throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
            }
            return userInfo;
        } catch (GuiguException e) {
            throw e;
        } catch (Exception e) {
            log.error("Redis operation failed", e);
            throw new GuiguException(ResultCodeEnum.SERVICE_ERROR);
        }
    }
}